package mat;


import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.log4j.Logger;

public class FileParser 
{
	protected String sFileName;
	protected String subFileName;
	protected IFileParser cb;
	protected Logger log;
	
	protected String encoding;
	
	public FileParser(String sFileName, IFileParser cb)
	{
		this(sFileName, null, cb, null);
	}
	
	public FileParser(String sFileName, IFileParser cb, String encoding)
	{
		this(sFileName, null, cb, encoding);
	}
	
	public FileParser(String sFileName, String subFileName, IFileParser cb)
	{
		this(sFileName, subFileName, cb, null);
	}
	
	public FileParser(String sFileName, String subFileName, IFileParser cb, String encoding)
	{
		this.sFileName = sFileName;
		this.subFileName = subFileName;
		this.cb = cb;
		this.log = Logger.getLogger(this.getClass());
		this.encoding = encoding;
	}
	
	protected int ProcessedRows;
	public int getProcessedRows()
	{
		return ProcessedRows;
	}
	
	protected long timeoutSecs = 60*60;
	public long getTimeoutSecs()
	{
		return timeoutSecs;
	}
	public void setTimeoutSecs(long timeoutSecs)
	{
		this.timeoutSecs = timeoutSecs;
	}

	protected long intervalCheckSecs = 30;
	public long getIntervalCheckSecs()
	{
		return intervalCheckSecs;
	}
	public void setIntervalCheckSecs(long intervalCheckSecs)
	{
		this.intervalCheckSecs = intervalCheckSecs;
	}
	
	protected MultiProtocolFile canReadAny(Collection<MultiProtocolFile> set) throws IOException
	{
		for (MultiProtocolFile f : set)
		{
			if (f.exists() && f.canRead())
				return f;
		}
		
		return null;
	}
	
	protected List<MultiProtocolFile> buildListOfFiles() throws IOException
	{
		LinkedList<MultiProtocolFile> listFilesToWatch = new LinkedList<MultiProtocolFile>();
		listFilesToWatch.add(new MultiProtocolFile(sFileName));
		listFilesToWatch.add(new MultiProtocolFile(sFileName + ".zip"));
		listFilesToWatch.add(new MultiProtocolFile(sFileName + ".gzip"));
		listFilesToWatch.add(new MultiProtocolFile(sFileName + ".gz"));
		
		if (sFileName.indexOf('.') > 1)
		{
			String fn = sFileName.substring(0, sFileName.lastIndexOf('.'));
			listFilesToWatch.add(new MultiProtocolFile(fn + ".zip"));
			listFilesToWatch.add(new MultiProtocolFile(fn + ".gzip"));
			listFilesToWatch.add(new MultiProtocolFile(fn + ".gz"));
		}
		
		return listFilesToWatch;
	}
	
	protected MultiProtocolFile waitForFile() throws IOException
	{
		List<MultiProtocolFile> listFilesToWatch = buildListOfFiles();
		
		long waitedSecs = 0;
		MultiProtocolFile f = null;
		while ((f = canReadAny(listFilesToWatch)) == null && waitedSecs <= timeoutSecs)
		{
			log.info("Wait " + intervalCheckSecs + " of " + waitedSecs + " / " + timeoutSecs + " for file to appear: " + sFileName);
			try 
			{
				Thread.sleep(intervalCheckSecs * 1000);
			} 
			catch (Exception e) 
			{
			}
			waitedSecs += intervalCheckSecs;			
		}
		
		return f;
	}
	
	
	public void processIfExists() throws FileParserException
	{
		ZipInputStream istr = null;
		try
		{
			List<MultiProtocolFile> listFilesToWatch = buildListOfFiles();
			MultiProtocolFile f = canReadAny(listFilesToWatch);
			if (f == null)
			{
				log.warn("FileParser cannot process: " + sFileName);
				return;
			}
			
			if (subFileName != null)
			{
				istr = new ZipInputStream(f.getInputStream());
				ZipEntry ze = null;
				boolean found = false;
				while ( (ze = istr.getNextEntry()) != null )
				{					
					String zeName = ze.getName();
					if (zeName.toLowerCase().indexOf(subFileName.toLowerCase()) >= 0)
					{
						found = true;
						break;
					}
				}
				istr.close();
				istr = null;
				
				if (!found)
				{
					log.warn("FileParser cannot process: " + subFileName + " @ " + sFileName);
					return;
				}
			}
			
			process(f);
		}
		catch (Exception e)
		{
			try
			{
				if (istr != null)
					istr.close();
			}
			catch (Exception ee)
			{
				
			}
			throw new FileParserException(e);
		}
	}
	
	public void process() throws FileParserException
	{
		try
		{
			MultiProtocolFile f = waitForFile();
			process(f);
		}
		catch (Exception e)
		{
			throw new FileParserException(e);
		}
	}
	
	
	protected void process(MultiProtocolFile f) throws FileParserException
	{
		LineNumberReader rdr = null;
		long totalBytes;		
		try
		{
			if (f == null || !f.exists())
				throw new FileParserException("File does not exist: " + sFileName);
			if (!f.canRead())
				throw new FileParserException("Cannot read file: " + sFileName);
			
			if (f.toString().endsWith(".zip"))
			{
				String fnOrig = new MultiProtocolFile(sFileName).getName();
				
				ZipInputStream istr = new ZipInputStream(f.getInputStream());
				ZipEntry ze = null;
				while ( (ze = istr.getNextEntry()) != null )
				{					
					String zeName = ze.getName();
					if (zeName.lastIndexOf('/') > 1)
						zeName = zeName.substring(zeName.lastIndexOf('/') + 1);
					
					if (subFileName==null)
					{
						// old logic:
						if (zeName.equalsIgnoreCase(fnOrig))
							break;
					}
					else 
					{
						// load subFileName instead of fnOrig
						if (zeName.equalsIgnoreCase(subFileName))
							break;
					}
					
					ze = null;
				}
				
				
				if (ze == null)
				{
					if (subFileName != null)
						throw new FileParserException("Cannot find '" + subFileName + "' in " + f);
					else
						throw new FileParserException("Cannot find '" + fnOrig + "' in " + f);
				}
				
				log.info("Start Reading from: " + ze.getName() + "@" + f);
				totalBytes = ze.getSize();				
				
				if(encoding==null)
					rdr = new LineNumberReader(new InputStreamReader(istr));
				else
					rdr = new LineNumberReader(new InputStreamReader(istr, encoding));
			}
			else if ((f.toString().endsWith(".gzip")) || (f.toString().endsWith(".gz")))
			{
				log.info("Start Reading from: " + getfileDescr(f));
				
				totalBytes = GZipFileUtils.getUncompressedSize(f);
				
				if(encoding==null)
					rdr = new LineNumberReader(new InputStreamReader(new GZIPInputStream(f.getInputStream())));
				else
					rdr = new LineNumberReader(new InputStreamReader(new GZIPInputStream(f.getInputStream()), encoding));
			}
			else
			{
				log.info("Start Reading from: " + getfileDescr(f));
				totalBytes = f.length();
				
				if(encoding==null)
					rdr = new LineNumberReader(new InputStreamReader(f.getInputStream()));
				else
					rdr = new LineNumberReader(new InputStreamReader(f.getInputStream(), encoding));
			}
			
			PercCounter cnt = new PercCounter((int) totalBytes);
			loopOnFileContents(cnt, rdr);
			ProcessedRows = rdr.getLineNumber();
			log.info("Total Rows Read : " + ProcessedRows);		
		}
		catch (Exception e)
		{
			throw new FileParserException(e);
		}
		finally
		{
			if (rdr != null)
			{
				try { rdr.close(); } catch (Exception e) { }
			}
		}
	}
	
	protected void loopOnFileContents(PercCounter cnt, LineNumberReader rdr) throws IOException
	{
		String row;
		
		while ((row = rdr.readLine()) != null)
		{
			if (cnt.incAndShouldSaySomething(row.length() + 2))
				log.info(cnt.getSomething());
			
			try
			{
				cb.OnParseFileRow(row);
			}
			catch (Throwable e)
			{
				log.warn("Error parsing row " + rdr.getLineNumber() + " '" + row + "'", e);
			}
		}
	}
	
	
	protected String getfileDescr(MultiProtocolFile f) throws IOException
	{
		return f + " " + f.length() + " " + new Date(f.lastModified());
	}
	
	public IFileParser getCb()
	{
		return cb;
	}
	
}